package com.csp.mingyue.auth.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.csp.mingyue.auth.dto.EmailLoginDto;
import com.csp.mingyue.auth.dto.PasswordLoginDto;
import com.csp.mingyue.auth.dto.RegisterDto;
import com.csp.mingyue.auth.dto.SmsLoginDto;
import com.csp.mingyue.common.core.constant.CacheConstants;
import com.csp.mingyue.common.core.constant.Constants;
import com.csp.mingyue.common.core.exception.CaptchaExpireException;
import com.csp.mingyue.common.core.exception.UserException;
import com.csp.mingyue.common.core.utils.AddressUtils;
import com.csp.mingyue.common.core.utils.ServletUtils;
import com.csp.mingyue.common.core.utils.SpringUtils;
import com.csp.mingyue.common.core.vo.R;
import com.csp.mingyue.common.log.enums.LoginLogTypeEnum;
import com.csp.mingyue.common.log.enums.LoginResultEnum;
import com.csp.mingyue.common.log.enums.OperatorUserType;
import com.csp.mingyue.common.log.event.LoginLogEvent;
import com.csp.mingyue.common.redis.utils.RedisUtils;
import com.csp.mingyue.common.satoken.utils.LoginHelper;
import com.csp.mingyue.system.api.entity.SysUser;
import com.csp.mingyue.system.api.feign.RemoteUserService;
import com.csp.mingyue.system.api.vo.LoginUser;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
 * 系统服务登录逻辑处理
 *
 * @author Strive
 * @date 2023/6/28 16:03
 */
@Service
@RequiredArgsConstructor
public class SysLoginService {

	private final RemoteUserService remoteUserService;

	public SaTokenInfo login(PasswordLoginDto dto) {
		// 远程调用用户服务
		R<LoginUser> userInfoResp = remoteUserService.userInfo(dto.getUsername());
		if (userInfoResp.getCode() == Constants.FAIL) {
			throw new UserException(userInfoResp.getMsg());
		}

		LoginUser userInfo = userInfoResp.getData();

		if (!userInfo.getPassword().equals(dto.getPassword())) {
			recordLoginLog(userInfo.getUserId(), userInfo.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME,
					LoginResultEnum.BAD_CREDENTIALS);
			throw new UserException("密码错误，请重试！");
		}

		if (dto.getUsername().equals(userInfo.getUsername()) && dto.getPassword().equals(userInfo.getPassword())) {
			// 第1步，先登录上
			LoginHelper.login(userInfo);
			// 第2步，获取 Token 相关参数
			SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

			recordLoginLog(userInfo.getUserId(), userInfo.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME,
					LoginResultEnum.SUCCESS);
			return tokenInfo;
		}

		return null;
	}

	public void logout() {
		try {
			LoginUser loginUser = LoginHelper.getLoginUser();
			recordLoginLog(loginUser.getUserId(), loginUser.getUsername(), LoginLogTypeEnum.LOGOUT_SELF,
					LoginResultEnum.SUCCESS);
		}
		catch (NotLoginException ignored) {
		}
		finally {
			try {
				StpUtil.logout();
			}
			catch (NotLoginException ignored) {
			}
		}
	}

	public SaTokenInfo smsLogin(SmsLoginDto dto) {
		/*
		 * 两种方式，这里选择（1） 1.查询号码是否存在，如果不存在则无法登录（一般面相B端）； 2.查询号码是否存在，不存在直接注册，继续登录（一般面相C端）；
		 */
		// 远程调用用户服务
		R<LoginUser> userInfoResp = remoteUserService.userInfoByPhone(dto.getPhone());
		if (userInfoResp.getCode() == Constants.FAIL) {
			throw new UserException(userInfoResp.getMsg());
		}

		// 校验验证码是否正确
		if (!checkSmsOrEmailCode(dto.getPhone(), dto.getSmsCode())) {
			throw new UserException("验证码错误");
		}

		LoginUser userInfo = userInfoResp.getData();

		if (dto.getPhone().equals(userInfo.getPhone())) {
			// 第1步，先登录上
			LoginHelper.login(userInfo);
			// 第2步，获取 Token 相关参数
			SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

			recordLoginLog(userInfo.getUserId(), userInfo.getUsername(), LoginLogTypeEnum.LOGIN_MOBILE,
					LoginResultEnum.SUCCESS);
			return tokenInfo;
		}

		return null;
	}

	/**
	 * 校验短信或邮箱验证码
	 * @param phoneOrEmail 手机号或邮箱
	 * @param code 验证码
	 * @return 验证码是否正确
	 */
	private Boolean checkSmsOrEmailCode(String phoneOrEmail, String code) {
		String key = CacheConstants.CAPTCHA_CODE_KEY + phoneOrEmail;
		String redisCode = RedisUtils.getCacheObject(key);

		if (StrUtil.isBlank(redisCode)) {
			throw new CaptchaExpireException();
		}

		return redisCode.equals(code);
	}

	public SaTokenInfo emailLogin(EmailLoginDto dto) {
		// 远程调用用户服务
		R<LoginUser> userInfoResp = remoteUserService.userInfoByEmail(dto.getEmail());
		if (userInfoResp.getCode() == Constants.FAIL) {
			throw new UserException(userInfoResp.getMsg());
		}

		// 校验验证码是否正确
		if (!checkSmsOrEmailCode(dto.getEmail(), dto.getEmailCode())) {
			throw new UserException("验证码错误");
		}

		LoginUser userInfo = userInfoResp.getData();

		if (dto.getEmail().equals(userInfo.getEmail())) {
			// 第1步，先登录上
			LoginHelper.login(userInfo);
			// 第2步，获取 Token 相关参数
			SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

			recordLoginLog(userInfo.getUserId(), userInfo.getUsername(), LoginLogTypeEnum.LOGIN_EMAIL,
					LoginResultEnum.SUCCESS);
			return tokenInfo;
		}

		return null;
	}

	/**
	 * 用户注册
	 * @param dto 用户注册入参
	 */
	public void register(RegisterDto dto) {
		// 远程调用用户服务
		R<Boolean> userInfoResp = remoteUserService.register(BeanUtil.copyProperties(dto, SysUser.class));
		if (userInfoResp.getCode() == Constants.FAIL) {
			throw new UserException(userInfoResp.getMsg());
		}

		if (!userInfoResp.getData()) {
			throw new UserException("用户注册失败！");
		}
	}

	public void recordLoginLog(Long userId, String username, LoginLogTypeEnum logTypeEnum,
			LoginResultEnum loginResult) {
		// 封装对象
		LoginLogEvent loginLogEvent = new LoginLogEvent();

		loginLogEvent.setLogType(logTypeEnum.getType());
		loginLogEvent.setResult(loginResult.getResult());

		String ip = ServletUtils.getClientIP();
		loginLogEvent.setReqIp(ServletUtils.getClientIP());
		loginLogEvent.setReqAddress(AddressUtils.getRealAddressByIP(ip));
		loginLogEvent.setUserAgent(Objects.requireNonNull(ServletUtils.getRequest()).getHeader(HttpHeaders.USER_AGENT));

		loginLogEvent.setUserId(userId);
		loginLogEvent.setUserName(username);
		loginLogEvent.setUserType(OperatorUserType.MANAGE.ordinal());

		// 发布登录日志事件
		SpringUtils.context().publishEvent(loginLogEvent);
	}

}
